package com.menggudf.dao.base;

import java.io.Serializable;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaBuilder.In;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class BaseQuery<T>
  implements Serializable
{
  private static final long serialVersionUID = 5064932771068929342L;
  private static final Log log = LogFactory.getLog(BaseQuery.class);
  private EntityManager entityManager;
  private Class<T> clazz;
  private Root from;
  private CriteriaQuery<T> criteriaQuery;
  private CriteriaBuilder criteriaBuilder;
  private List<Predicate> predicates;
  private List<Order> orders;
  
  private BaseQuery() {}
  
  public BaseQuery(Class<T> clazz, EntityManager entityManager)
  {
    this.clazz = clazz;
    this.entityManager = entityManager;
    this.criteriaBuilder = this.entityManager.getCriteriaBuilder();
    this.criteriaQuery = this.criteriaBuilder.createQuery(this.clazz);
    
    this.from = this.criteriaQuery.from(this.clazz);
    
    this.predicates = new ArrayList();
    this.orders = new ArrayList();
  }
  
  public void eq(String propertyName, Object value)
  {
    if (isNullOrEmpty(value)) {
      return;
    }
    this.predicates.add(this.criteriaBuilder.equal(this.from.get(propertyName), value));
  }
  
  private boolean isNullOrEmpty(Object value)
  {
    if ((value instanceof String)) {
      return (value == null) || ("".equals(value));
    }
    return value == null;
  }
  
  public void isNull(String propertyName)
  {
    this.predicates.add(this.criteriaBuilder.isNull(this.from.get(propertyName)));
  }
  
  public void isNotNull(String propertyName)
  {
    this.predicates.add(this.criteriaBuilder.isNotNull(this.from.get(propertyName)));
  }
  
  public void notEq(String propertyName, Object value)
  {
    if (isNullOrEmpty(value)) {
      return;
    }
    this.predicates.add(this.criteriaBuilder.notEqual(this.from.get(propertyName), 
      value));
  }
  
  public void in(String propertyName, Collection value)
  {
    if ((value == null) || (value.size() == 0)) {
      return;
    }
    if (value.size() == 1)
    {
      eq(propertyName, value.iterator().next());
    }
    else
    {
      Iterator<?> iterator = value.iterator();
      CriteriaBuilder.In in = this.criteriaBuilder.in(this.from.get(propertyName));
      while (iterator.hasNext()) {
        in.value(iterator.next());
      }
      this.predicates.add(in);
    }
  }
  
  public void notIn(String propertyName, Collection value)
  {
    if ((value == null) || (value.size() == 0)) {
      return;
    }
    Iterator iterator = value.iterator();
    CriteriaBuilder.In in = this.criteriaBuilder.in(this.from.get(propertyName));
    while (iterator.hasNext()) {
      in.value(iterator.next());
    }
    this.predicates.add(this.criteriaBuilder.not(in));
  }
  
  public void like(String propertyName, String value)
  {
    if (isNullOrEmpty(value)) {
      return;
    }
    if (value.indexOf("%") < 0) {
      value = "%" + value + "%";
    }
    this.predicates.add(this.criteriaBuilder.like(this.from.get(propertyName), value));
  }
  
  public void between(String propertyName, Date lo, Date go)
  {
    between(propertyName, lo, go, true);
  }
  
  public void between(String propertyName, Date lo, Date go, boolean dateBoundaryFlag)
  {
    if ((!isNullOrEmpty(lo)) && (!isNullOrEmpty(go)))
    {
      if (dateBoundaryFlag) {
        try
        {
          lo = DateUtils.parseDate(
            DateFormatUtils.format(lo, "yyyyMMdd") + "000000", new String[] {
            "yyyyMMddHHmmss" });
          
          go = DateUtils.parseDate(
            DateFormatUtils.format(go, "yyyyMMdd") + "235959", new String[] {
            "yyyyMMddHHmmss" });
        }
        catch (ParseException e)
        {
          log.error("parse failed", e);
        }
      }
      this.predicates.add(this.criteriaBuilder.between(this.from.get(propertyName), 
        lo, go));
    }
  }
  
  public void between(String propertyName, Number lo, Number go)
  {
    if (!isNullOrEmpty(lo)) {
      ge(propertyName, lo);
    }
    if (!isNullOrEmpty(go)) {
      le(propertyName, go);
    }
  }
  
  public void le(String propertyName, Number value)
  {
    if (isNullOrEmpty(value)) {
      return;
    }
    this.predicates.add(this.criteriaBuilder.le(this.from.get(propertyName), value));
  }
  
  public void lt(String propertyName, Number value)
  {
    if (isNullOrEmpty(value)) {
      return;
    }
    this.predicates.add(this.criteriaBuilder.lt(this.from.get(propertyName), value));
  }
  
  public void ge(String propertyName, Number value)
  {
    if (isNullOrEmpty(value)) {
      return;
    }
    this.predicates.add(this.criteriaBuilder.ge(this.from.get(propertyName), value));
  }
  
  public void gt(String propertyName, Number value)
  {
    if (isNullOrEmpty(value)) {
      return;
    }
    this.predicates.add(this.criteriaBuilder.gt(this.from.get(propertyName), value));
  }
  
  public void addOrder(String propertyName, String order)
  {
    if (propertyName == null) {
      return;
    }
    order = order == null ? "asc" : order;
    if (this.orders == null) {
      this.orders = new ArrayList();
    }
    if (order.equalsIgnoreCase("asc")) {
      this.orders.add(this.criteriaBuilder.asc(this.from.get(propertyName)));
    } else if (order.equalsIgnoreCase("desc")) {
      this.orders.add(this.criteriaBuilder.desc(this.from.get(propertyName)));
    }
  }
  
  public void setOrder(String propertyName, String order)
  {
    this.orders = null;
    addOrder(propertyName, order);
  }
  
  public Class<T> getModleClass()
  {
    return this.clazz;
  }
  
  public Class<T> getClazz()
  {
    return this.clazz;
  }
  
  public List<Order> getOrders()
  {
    return this.orders;
  }
  
  public void setOrders(List<Order> orders)
  {
    this.orders = orders;
  }
  
  public EntityManager getEntityManager()
  {
    return this.entityManager;
  }
  
  public void setEntityManager(EntityManager em)
  {
    this.entityManager = em;
  }
  
  public Root getFrom()
  {
    return this.from;
  }
  
  public List<Predicate> getPredicates()
  {
    return this.predicates;
  }
  
  public void setPredicates(List<Predicate> predicates)
  {
    this.predicates = predicates;
  }
  
  public CriteriaQuery<T> getCriteriaQueryForCount()
  {
    if ((this.predicates != null) && (this.predicates.size() > 0)) {
      this.criteriaQuery = this.criteriaQuery.where(
        (Predicate[])this.predicates.toArray(new Predicate[this.predicates.size()]));
    }
    this.criteriaQuery = this.criteriaQuery.orderBy(new ArrayList());
    
    return this.criteriaQuery;
  }
  
  public CriteriaQuery<T> getCriteriaQuery()
  {
    if ((this.predicates != null) && (this.predicates.size() > 0)) {
      this.criteriaQuery = this.criteriaQuery.where(
        (Predicate[])this.predicates.toArray(new Predicate[this.predicates.size()]));
    }
    if ((this.orders != null) && (this.orders.size() > 0)) {
      this.criteriaQuery = this.criteriaQuery.orderBy(this.orders);
    }
    return this.criteriaQuery;
  }
  
  public CriteriaBuilder getCriteriaBuilder()
  {
    return this.criteriaBuilder;
  }
}
